<?php

/**
 * Created by PhpStorm.
 * User: superx
 * Date: 14-5-8
 * Time: 下午12:26
 */
abstract class My_BatchDao extends Core_Dao {

    /**
     * no memcache support if empty
     */
    public $mcPrefix = '';
    public $mcConnName = 'default';
    public $L1Enabled = true;

    protected static $_cache_limit = 5000;

    protected static $_cache = [];
    protected static $_cache_len = [];

    /**
     * @return $this
     */
    public static function getInstance() {
        $instance = parent::getInstance();
        $id = $instance->getIdentity();
        $instance->L1Enabled = !(PHP_SAPI == 'cli') && $instance->mcPrefix;
        if (!isset(self::$_cache[$id])) {
            self::$_cache[$id] = [0 => false, '' => false];
            self::$_cache_len[$id] = 0;
        }
        return $instance;
    }

    /**
     * @param $pk
     * @return Core_Model|false
     */
    public function find($pk) {
        if (!$pk) {
            return false;
        }
        $row = $this->getCache($pk);
        if (is_null($row)) {
            if ($this->mcPrefix) {
                $row = $this->setCache($pk, $this->findFromMemcache($pk));
            } else {
                $row = $this->setCache($pk, $this->findFromDb($pk));
            }
        }
        return $row;
    }

    /**
     * @param $pk
     * @return Core_Model|false
     */
    public function findFromDb($pk) {
        return parent::find($pk);
    }

    public function findFromMemcache($pk) {
        if (!$this->mcPrefix) {
            return false;
        }
        $mc_key = $this->mcPrefix . ':' . $pk;
        return Core_Memcache::getInstance($this->mcConnName)
            ->getAndSave($mc_key, array($this, 'findFromDb'), array($pk));
    }

    /**
     * @param $data
     * @param $pk
     * @return int
     */
    public function update($data, $pk) {
        $ret = parent::update($data, $pk);
        if ($ret) {
            if ($this->mcPrefix) {
                $mc_key = $this->mcPrefix . ':' . $pk;
                Core_Memcache::getInstance($this->mcConnName)->del($mc_key);
            }
            $this->clearCache($pk);
        }
        return $ret;
    }

    public function updateWhere(array $data, array $condition) {
        $table = $this->getTable();
        $prelimit = 2000;
        $curernt_id = 0;
        do {
            $clear_cache_ids = $this->getQuery()->where($condition)
                ->where($this->getPK() . " > ?", $curernt_id)
                ->order($this->getPK() . " ASC")
                ->limit($prelimit)
                ->fetchPairs($table->getPK(), $table->getPk());

            $count = 0;
            // 清缓存
            foreach ($clear_cache_ids as $id) {
                if ($this->mcPrefix) {
                    $mc_key = $this->mcPrefix . ':' . $id;
                    Core_Memcache::getInstance($this->mcConnName)->del($mc_key);
                }
                $this->clearCache($id);

                $curernt_id = $id;
                $count++;
            }
        } while ($count == $prelimit);

        $result = $this->getTable()->updateWhere($data, $condition, null)->execute();
        return $result;
    }

    /**
     * @param $pk
     * @return int
     */
    public function delete($pk) {
        $ret = parent::delete($pk);
        if ($ret) {
            if ($this->mcPrefix) {
                $mc_key = $this->mcPrefix . ':' . $pk;
                Core_Memcache::getInstance($this->mcConnName)->del($mc_key);
            }
            $this->clearCache($pk);
        }
        return $ret;
    }


    /**
     * @param array $pks
     * @return array
     */
    public function findAll(array $pks) {
        if (!$pks) {
            return [];
        }

        array_walk($pks, function (&$val, $key) {
            $val = (string)$val;
        });
        $pks = array_filter(array_unique($pks));
        $results = array_fill_keys($pks, false);

        $pk_name = $this->getPK();

        $load_miss = array();
        foreach ($pks as $pk) {
            if ($this->L1Enabled && ($row = $this->getCache($pk))) {
                $results[$pk] = $row;
            } else {
                //L1 miss, try L2
                $load_miss[$pk] = 1;
            }
        }

        if ($load_miss) {
            //L1 miss, try mc
            if ($this->mcPrefix) {
                $get_mc = array_map(function ($val) {
                    return $this->mcPrefix . ':' . $val;
                }, array_keys($load_miss));
                $mc_rows = Core_Memcache::getInstance($this->mcConnName)->getMulti($get_mc);

                Core_Log::debug('L1 miss, mc batch', $get_mc, 'debugBatch');

                foreach ($mc_rows as $row) {
                    $key = strval($row[$pk_name]);
                    $results[$key] = $row;
                    $this->setCache($key, $row);
                    unset($load_miss[$key]);
                }

            }

            //mc miss, try db
            if ($load_miss) {
                Core_Log::debug('mc miss', $load_miss, 'debugBatch');

                $db_rows = $this->findAllBatch(array_keys($load_miss));
                if ($db_rows) {
                    $set_mc = array();
                    foreach ($db_rows as $key => $row) {
                        $key = strval($key);
                        $results[$key] = $row;
                        $this->setCache($key, $row);
                        if ($this->mcPrefix) {
                            $set_mc[$this->mcPrefix . ':' . $key] = $row;
                        }
                    }
                    if ($this->mcPrefix && $set_mc) {
                        Core_Log::debug('load from db', array_keys($set_mc), 'debugBatch');
                        Core_Memcache::getInstance($this->mcConnName)->setMulti($set_mc);
                    }
                }
            }
        }
        return array_filter($results);
    }

    /**
     * false:没找到  null:未加载
     * @param $pk
     * @return Core_Model|false|null
     */
    protected function getCache($pk) {
        if (!$this->L1Enabled) {
            return null;
        }
        $pk = strval($pk);
        return isset(self::$_cache[$this->_identity][$pk]) ?
            self::$_cache[$this->_identity][$pk] : null;
    }

    protected function setCache($pk, $row) {
        if ($this->L1Enabled) {
            $pk = strval($pk);
            if (self::$_cache_len[$this->_identity] > self::$_cache_limit) {
                self::$_cache[$this->_identity] = [0 => false, '' => false];
                self::$_cache_len[$this->_identity] = 0;
            }

            self::$_cache[$this->_identity][$pk] = $row;
            self::$_cache_len[$this->_identity]++;
        }
        return $row;
    }

    protected function clearCache($pk) {
        if ($this->L1Enabled) {
            $pk = strval($pk);
            unset(self::$_cache[$this->_identity][$pk]);
        }
    }

    public function clearMemcache($pk) {
        if ($this->mcPrefix) {
            $mc_key = $this->mcPrefix . ':' . $pk;
            Core_Memcache::getInstance($this->mcConnName)->del($mc_key);
        }
        // 进程缓存清除
        $this->clearCache($pk);
    }

}